package com.xnx.bincker.leader.core.security;

import com.xnx.bincker.leader.domain.Menu;
import com.xnx.bincker.leader.domain.User;
import com.xnx.bincker.leader.exception.login.LoginFailureException;
import com.xnx.bincker.leader.service.MenuService;
import com.xnx.bincker.leader.service.UserService;
import com.xnx.bincker.leader.utils.ArraysUtils;
import com.xnx.bincker.leader.utils.CommonUtils;
import com.xnx.bincker.leader.utils.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.*;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private final UserService userService;
    private final MenuService menuService;
    private final SecurityMetadataSource securityMetadataSource;
    private final AccessDecisionManager accessDecisionManager;

    @Autowired
    public SecurityConfiguration(
            UserService userService,
            MenuService menuService,
            SecurityMetadataSource securityMetadataSource,
            AccessDecisionManager accessDecisionManager
            ) {
        this.userService = userService;
        this.menuService = menuService;
        this.securityMetadataSource = securityMetadataSource;
        this.accessDecisionManager = accessDecisionManager;
    }

    @Bean
	public PasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}

	/**
	 * 登录成功处理
	 */
	private AuthenticationSuccessHandler authenticationSuccessHandler(){
    	return (request, response, authentication) -> {
    		User user = (User) authentication.getPrincipal();
    		List<Menu> menuList = menuService.queryMenuListByRoleCode(user.getRoles());
            Map<String, Object> model = new HashMap<>();
            model.put("userInfo", user);
            model.put("menuList", menuList);
            CommonUtils.responseJsonLimit(request, response, model,
                    new Pair<>(User.class, ArraysUtils.asSet("nickname")),
                    new Pair<>(Menu.class, ArraysUtils.asSet("name", "children", "url")));
	    };
	}

    /**
     * 登录失败处理
     */
	private AuthenticationFailureHandler authenticationFailureHandler(){
	    return (httpServletRequest, httpServletResponse, e) -> {
	        if(e instanceof BadCredentialsException){
	            throw new LoginFailureException("密码错误");
            }
            throw new LoginFailureException();
        };
    }

    @Bean
    public AbstractAuthenticationProcessingFilter usernamePasswordAuthenticationFilter(){
	    CustomUsernamePasswordAuthenticationFilter filter =
                new CustomUsernamePasswordAuthenticationFilter(
                        new AntPathRequestMatcher("/authentication", RequestMethod.POST.name())
                );
        try {
            filter.setAuthenticationManager(authenticationManager());
            filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
            filter.setAuthenticationFailureHandler(authenticationFailureHandler());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return filter;
    }

    @Override
    protected UserDetailsService userDetailsService() {
        return userService;
    }

    @Override
    public void init(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers(HttpMethod.OPTIONS)
                .antMatchers("/js/**", "/style/**", "/image/**", "/upload/**")
                ;
        super.init(web);
    }

    @Override
	protected void configure(HttpSecurity http) throws Exception {
		http
                .authorizeRequests()
                .and()
                .logout()
				.logoutUrl("/logout")
                .logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication)->{})
				.permitAll()
				;
//		单点登录
		http.sessionManagement()
				.sessionAuthenticationErrorUrl("/authenticationError")
				.maximumSessions(1)
				;
//		csrf配置
		http.csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .requireCsrfProtectionMatcher(httpServletRequest -> {
            String method = httpServletRequest.getMethod();
            return method.equalsIgnoreCase(RequestMethod.POST.name()) || method.equalsIgnoreCase(RequestMethod.PUT.name());
        });
//		自定义登录接口
		http.addFilterBefore(
		        usernamePasswordAuthenticationFilter(),
                UsernamePasswordAuthenticationFilter.class
        );
//		自定义FilterSecurityInterceptor(权限认证)
		http.addFilterAfter(new FilterSecurityInterceptor(securityMetadataSource, accessDecisionManager),
                org.springframework.security.web.access.intercept.FilterSecurityInterceptor.class);
	}
}
